using System.Collections.Generic;
using System.Threading;

namespace AsyncLibrary.Internal
{
  /// <summary>Completely thread safe queue</summary>
  /// <remarks><example>
  /// This example creates two threads. <list type="bullet">
  /// <item>The first reads user input and enqueues strings into common SynchronizedQueue.</item>
  /// <item>The second takes strings from queue and shows it on console.</item>
  /// </list><code><![CDATA[
  /// 
  /// class Program
  /// {
  ///   static void Main(string[] args)
  ///   {
  ///     new Thread(ThreadA).Start();
  ///     new Thread(ThreadB).Start();
  ///   }
  /// 
  ///   static readonly SynchronizedQueue<string>
  ///     _queue = new SynchronizedQueue<string>();
  /// 
  ///   static void ThreadA()
  ///   {
  ///     while (true)
  ///     {
  ///       // This call blocks thread A waiting for user input
  ///       string line = Console.ReadLine();
  ///       // This call enqueues line and 
  ///       // unblocks thread B if queue is empty
  ///       _queue.Enqueue(line);
  ///     }
  ///   }
  /// 
  ///   static void ThreadB()
  ///   {
  ///     while (true)
  ///     {
  ///       // This call blocks thread B if queue is empty
  ///       string line = _queue.Dequeue();
  ///       Console.WriteLine(line);
  ///     }
  ///   }
  /// }
  /// ]]></code></example></remarks>
  internal class SynchronizedQueue<T>
  {
    private readonly Queue<T> _queue = new Queue<T>();
    private readonly Semaphore _semaphore = new Semaphore(0, int.MaxValue);

    private readonly object _queueLocker = new object();
    private readonly object _enqueueLocker = new object();
    private readonly object _dequeueLocker = new object();

    /// <summary>Unblocks <see cref="Dequeue"/> if it has already been called</summary>
    public void Enqueue(T item)
    {
      lock (_enqueueLocker)
      {
        lock (_queueLocker)
          _queue.Enqueue(item);
        _semaphore.Release();
      }
    }
    /// <summary>Dequeues item if queue is not empty, -or- 
    ///   blocks control flow waitig another thread to add item</summary>
    public T Dequeue()
    {
      lock (_dequeueLocker)
      {
        _semaphore.WaitOne();
        lock (_queueLocker)
          return _queue.Dequeue();
      }
    }
    public void Clear()
    {
      lock (_enqueueLocker)
      {
        while (!Monitor.TryEnter(_dequeueLocker))
        {
          lock (_queueLocker)
            if (_queue.Count == 0)
              return;
          Thread.Sleep(0);
        }
        try
        {
          foreach (var t in _queue)
            _semaphore.WaitOne();
          _queue.Clear();
        }
        finally
        {
          Monitor.Exit(_dequeueLocker);
        }
      }
    }
  }
}